/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.googlecode.datawander.codegenerator;

import com.db4o.foundation.NotImplementedException;
import com.googlecode.datawander.i18n.I18n;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;

/**
 *
 * @author Marcin Stachniuk
 */
public class RelationalDatabaseScriptGenerator {

    private static final Logger logger = Logger.getLogger(SourceCodeGenerator2Object.class);
    
    /**
     * Name of generated primary key
     */
    public static final String PRIMARY_KEY_NAME = "GENERATED_ID";

    /**
     * Relational database type for primary key
     */
    public static final String PRIMARY_KEY_TYPE_RELATIONAL = "NUMBER(19)";

    /**
     * Object type equivalent PRIMARY_KEY_TYPE_RELATIONAL
     */
    public static final String PRIMARY_KEY_TYPE_OBJECT = "long";

    /**
     * Prefix use to generate name foreign key
     */
    public static final String ADDED_FOREIGN_KEY_PREFIX = "GEN_";

    /**
     * Prefix use to generate name of foreign key in inheritance
     */
    public static final String INHERITANCE_KEY_PREFIX = "INHERITANCE_";

    private static final String FOREGIN_KEY_PREFIX = "FK_";

    private static final String PRIMARY_KEY_PREFIX = "PK_";

    /**
     * Prefix for name of asociacive tables.
     */
    public static final String ASOCIACION_TABLE_PREFIX = "ASOC_";

    /**
     * Prefix for names of table with represents table of primitive types.
     */
    private static final String PRIMITIVE_TAB_NAME_PREFIX = "TAB_";

    private final static String INDEX_PREFIX = "INDEX_";

    private final static String INDEX_RELATIONAL_TYPE = "NUMBER(10)";

    private final static String INDEX_OBJECT_TYPE = "int";

    /**
     * New line char. Use to generate new line in file.
     */
    public static final String NEW_LINE;

    /**
     * String with one ident in files with source code
     */
    private static final String INDENT = "    ";

    /**
     * Constant that is a maximum number of chars in Oracle table and field
     * name.
     */
    public static final int MAX_NUMBER_CHARS_IN_NAME = 30;

    private static final Set<String> PRIMITIVES_NAMES;

    // static inicialize block
    static {
        NEW_LINE = System.getProperty("line.separator");
        PRIMITIVES_NAMES = new HashSet<String>();
        PRIMITIVES_NAMES.add("byte");
        PRIMITIVES_NAMES.add("char");
        PRIMITIVES_NAMES.add("short");
        PRIMITIVES_NAMES.add("int");
        PRIMITIVES_NAMES.add("long");
        PRIMITIVES_NAMES.add("boolean");
        PRIMITIVES_NAMES.add("float");
        PRIMITIVES_NAMES.add("double");
    }

    private static String [] wrappersTypes = new String [] {
        "java.lang.Byte",
        "java.lang.Character",
        "java.lang.Short",
        "java.lang.Integer",
        "java.lang.Long",
        "java.lang.Boolean",
        "java.lang.Float",
        "java.lang.Double"
    };

    private static String [] anotherSimpleSuprtedTypes = new String [] {
        "java.math.BigDecimal",
        "java.sql.Date",
        "java.util.Date",
        "java.lang.String",
        "java.sql.Timestamp",
        "oracle.sql.TIMESTAMP"
    };

    /**
     * Set of user objects form db4o
     */
    private Set<Class> loadedClassSet;

    /**
     * Map used to have generatet informations about primary keys and foregins
     * keys.
     */
    private Map<String, GeneratedDatabaseFields> generatetField;

    /**
     * Tab with transformated types
     */
    private final String TRANSLATE_TYPES_TAB[][] = new String[][] {
            {"java.lang.String", "NVARCHAR2(2000)"},
            {"byte", "CHAR(3)"},
            {"java.lang.Byte", "CHAR(3)"},
            {"char", "NVARCHAR2(1)"},
            {"java.lang.Character", "NVARCHAR2(1)"},
            {"short", "NUMBER(5)"},
            {"java.lang.Short", "NUMBER(5)"},
            {"int", "NUMBER(10)"},
            {"java.lang.Integer", "NUMBER(10)"},
            {"long", "NUMBER(19)"},
            {"java.lang.Long", "NUMBER(19)"},
            {"boolean", "CHAR(1)"},
            {"java.lang.Boolean", "CHAR(1)"},
            {"float", "NUMBER"},
            {"java.lang.Float", "NUMBER"},
            {"double", "NUMBER"},
            {"java.lang.Double", "NUMBER"},
            {"java.math.BigDecimal", "NUMBER"},
            {"java.sql.Date", "DATE"},
            {"java.util.Date", "DATE"},
            {"java.sql.Timestamp", "DATE"},
            {"oracle.sql.TIMESTAMP", "TIMESTAMP"}
    };

    private static Map<String, String> translateTypesMap;

    private static final String [] COLLECTIONS_CLASS_NAMES = new String [] {
            "java.util.Collection",
            "java.util.List<E>",
            "java.util.Set<E>"
    };

    /**
     * Map contain associative tables
     */
    private Map<String, GeneratedDatabaseFields> associativeTables;

    /**
     * Map contain objectNames (in List) that have to save after save actual
     * object (String). E.g. If class MyClass have a char [] tab, in this
     * Map is <MyClass <ClassNameForTab>>
     */
    private Map<String, List<ClassToSaveAfter>> objectsToAfterSave;

    /**
     * Generate relational database create script.
     * @param outputFileName Output file name where script is generated.
     * @param loadedClassSet 
     * @throws CodeGenerationException
     */
    public void generateCreateRelationalDatabaseScript(String outputFileName,
            Set<Class> loadedClassSet) throws CodeGenerationException {
        logger.trace("generateCreateRelationalDatabaseScript("+outputFileName+", ...)");

        this.loadedClassSet = loadedClassSet;

        File file = new File(outputFileName);
        try {
            file.createNewFile();
        } catch(IOException e) {
            throw new CodeGenerationException(e.getMessage());
        }

        if(!file.isFile()) {
            String message = I18n.getString("thispathisnotafile");
            message = String.format(message, file.getAbsolutePath());
            throw new CodeGenerationException(message);
        }

        generateAdditionalInformationForClasses();

        File outputFile = new File(outputFileName);
        String msg = I18n.getString("generatedscriptpath");
        msg = String.format(msg, outputFile.getAbsolutePath());
        logger.debug(msg);
        try {
            PrintWriter out = new PrintWriter(outputFile);
            generateScript(out, loadedClassSet);
            logger.info(I18n.getString("realationaldatabasescriptgeneratesucess"));
        } catch(FileNotFoundException e) {
            String message = I18n.getString("filenotfound");
            message = String.format(message, outputFile.getAbsolutePath());
            throw new CodeGenerationException(message);
        }
    }

    private void generateScript(PrintWriter out, Set<Class> storedClasses) {
        generateScriptDropCommands(out, storedClasses);

        generateScriptCreateTableCommands(out, storedClasses);

        out.close();
    }

    private void generateScriptDropCommands(PrintWriter out, Set<Class> storedClasses) {
        for(Class rc : storedClasses) {
            if(hasClassDeclaredFields(rc)) {
                out.append("drop table \"" + limitNameForMainClasses(rc.getName()) +
                        "\" cascade constraints;" + NEW_LINE + NEW_LINE);
            }
        }
        if(associativeTables != null) {
            for(String s: associativeTables.keySet()) {
                out.append("drop table \"" + s + "\" cascade constraints;" + NEW_LINE + NEW_LINE);
            }
        }
    }

    private void generateScriptCreateTableCommands(PrintWriter out, Set<Class> storedClasses) {
        for(Class rc : storedClasses) {
            generateScriptCreateTable(out, rc);
        }

        if(associativeTables != null) {
            Set<Map.Entry<String, GeneratedDatabaseFields>> mySet = associativeTables.entrySet();
            for(Map.Entry<String, GeneratedDatabaseFields> entry : mySet) {
                generateScriptCreateTable(out, entry.getValue(), entry.getKey());
            }
        }
    }

    private void generateScriptCreateTable(PrintWriter out, Class rc) {
        if(!hasClassDeclaredFields(rc)) {
            String message = I18n.getString("nogeneratescriptforclass");
            message = String.format(message, rc.getName());
            logger.debug(message);
            return;
        }
        
        out.append("create table \"" + limitNameForMainClasses(rc.getName()) +
                "\" (" + NEW_LINE);

        generatePrimaryKeys(out, rc);

        generateScriptForeignAndInheritanceKeys(out, rc);

        int numberOfGeneratedFields = calculateNumberOfGeneratedFields(rc.getDeclaredFields());
        int fieldNumber = 0;
        for(int i=0; i < rc.getDeclaredFields().length; i++) {
            Field rf = rc.getDeclaredFields()[i];
            String typeName = rf.getType().getName();
            if(isGeneratedColumnForField(rf)) {
                fieldNumber++;
                out.append(INDENT + "\"" + rf.getName() + "\"" + INDENT +
                        translateObjectTypeToRelationalType(typeName));
                if(fieldNumber == numberOfGeneratedFields) {
                    out.append(NEW_LINE);
                } else {
                    out.append("," + NEW_LINE);
                }
            }
        }

        out.append(");" + NEW_LINE + NEW_LINE);
    }

    private void generatePrimaryKeys(PrintWriter out, Class rc) {
        out.append(INDENT + "\"" + PRIMARY_KEY_NAME + "\"" + INDENT +
                PRIMARY_KEY_TYPE_RELATIONAL + ", " + NEW_LINE);
    }

    private void generateScriptForeignAndInheritanceKeys(PrintWriter out, Class rc) {
        int numberOfGeneratedFields = calculateNumberOfGeneratedFields(rc.getDeclaredFields());
        GeneratedDatabaseFields dataField = generatetField.get(rc.getName());
        if(dataField != null) {
            int i=0;
            for(ForeignKey fk : dataField.getForeignKeys()) {
                logger.debug("Generate foregin key: " + fk.getName() + " type " 
                        + fk.getRelationalType());
                out.append(INDENT + "\"" + fk.getName() + "\"" + INDENT + 
                        fk.getRelationalType());
                i++;
                if(i != dataField.getForeignKeys().size()) {
                    out.append(",");
                } else {
                    if(numberOfGeneratedFields != 0) {
                        out.append(",");
                    }
                }
                out.append(NEW_LINE);
            }
        }
    }


    /**
     * Generate additional information that need to create create database script.
     */
    private void generateAdditionalInformationForClasses() {
        generatetField = new HashMap<String, GeneratedDatabaseFields>();
        generateForeignKeysInformation();
        generateInheritanceInformation();
    }

    /**
     * Method return Type name for values from tab.
     * @param field Field for that is generated type of tab. Field object should
     * be array.
     * @return Type name for object from tab.
     */
    public String getArrayTypeFromField(Field field) {
        logger.trace("getArrayTypeFromField(field name: " + field.getName() +
                " field type:" + field.getType().getName() + 
                " component type " + field.getType().getComponentType());
        return field.getType().getComponentType().getName();
    }

    private void generateForeignKeysInformation() {
        logger.trace("generateForeignKeysInformation() " + loadedClassSet.size());
        for(Class rc : loadedClassSet) {
            System.out.println("class: "+rc.getName() + " nr of fields " +
                    rc.getFields().length + " all " + rc.getDeclaredFields().length);
            for(Field rf : rc.getDeclaredFields()) {
                System.out.println("rf name: " + rf.getName() + " type: " +
                        rf.getType().getName() + " is Primitive: " + rf.getType().isPrimitive());
            }
            for(Field rf : rc.getDeclaredFields()) {
                if(rf.getType().isArray()) {
                    String arrayType = getArrayTypeFromField(rf);
                    System.out.println("Generate foreign key for: "+rf.getName() + " in "+rc.getName() + " arr type: "+arrayType);
                    if(isUserStoredClass(arrayType)) {
                        // test is this M:N or 1:N
                        if(hasClassTabReferencedToThisClass(arrayType, rc.getName())) {
                            // M:N
//                            Field refField = getTabReferencedToThisClass(arrayType, rc.getName());
                            addTypeMToNArrayReference(rc, rf);
                        } else {        // 1:N
                            System.out.println("dodajemy pole typu 1:N");
                            //TODO: test
                            // addType1ToN(arrayType, rc, rf);
                            addTypeMtoNForAssociacion1toNArrayReference(arrayType, rc, rf);
                        }
                    } else {        // is prymitve or embdemed type
                        logger.trace("Get generatetField from " + rf.getType().getName());
                        if(isPrimitiveType(arrayType)) {
                            logger.trace("Primitive tab type: " + arrayType);
                            addTableForPrimitiveArray(arrayType, rc, rf);
                        } else if(isFieldPrimitiveWrapper(arrayType)) {
                            addTableForWrapersArray(arrayType, rc, rf);
                        } else if(isFieldAnotherSimpleSuportedType(arrayType)) {
                            addTableForAnotherSimpleSuportedtype(arrayType, rc, rf);
                        } else {
                            //addType1ToN(arrayType, rc, rf);
                            addTypeMtoNForAssociacion1toNArrayReference(arrayType, rc, rf);
                        }
                    }
                } else if(isFieldCollection(rf)) {
                    System.out.println("Wykryto kolekcje "+rf.getName() + " in class "+rc.getName());
                    Type genType = rf.getGenericType();
                    System.out.println("Gen type: "+genType);
                    String genericType = getGenericTypeFromType(genType);
                    if(hasClassTabReferencedToThisClass(genericType, rc.getName())) {
                        // M:N
                        System.out.println("Mamy N:M");
                        //TODO: test
//                        Field refField = getTabReferencedToThisClass(genericType,
//                                rc.getName());
                        addTypeMToNCollectionReference(rc, rf);
                    } else {
                        System.out.println("Mamy 1:N");
                        //addType1ToN(genericType, rc, rf);
                        addTypeMtoNForAssociacion1toNCollecitonReference(genericType, rc, rf);
                    }
                } else if (isUserStoredClass(rf.getType().getName())) {
                    logger.debug("Wykryto asocjacje 1:1 z obiektem usera "+rf.getDeclaringClass().getName() +
                            " rf name " + rf.getName() + " aaa "+rf.getType().getName());
                    //addType1to1(rc, rf);
                    addType1ToN(rf.getType().getName(), rc, rf);
                }

            }
        }
    }

//    /**
//     * Method add ForeginKey to one table.
//     * @param rc
//     * @param rf
//     */
//    private void addType1to1(Class rc, Field rf) {
//        logger.trace("addType1to1(" + rc.getName() + ", " + rf.getName() + ");");
//// TRACE - addType1to1(bigcompanyportal.Boss, secretary);
//// TRACE - addRelationalFieldFK(bigcompanyportal.Secretary, bigcompanyportal.Boss, GEN_secretary_bigcompanyportal, NUMBER(19), long, true
//
//// TRACE - addType1to1(bigcompanyportal.Boss, secretary);
//// TRACE - addRelationalFieldFK(bigcompanyportal.Boss, bigcompanyportal.Secretary, GEN_secretary_bigcompanyportal, NUMBER(19), long, secretary, true);
//
//        // TRACE - found foreging keys: GEN_secretary_bigcompanyportal objType: long
//                    // owner table: bigcompanyportal.Boss refTabName: bigcompanyportal.Secretary
//                    // refTablPK: GENERATED_ID relType: NUMBER(19) unique: true
//
//        String addTabName = rf.getType().getName();
//        String refTableName = rf.getDeclaringClass().getName();
//        GeneratedDatabaseFields fields = generatetField.get(refTableName);
//        if (fields == null) {
//            fields = new GeneratedDatabaseFields();
//        }
//        fields.addRelationalFieldFK(refTableName, addTabName,
//                limitNameForeginKey(rc, rf), PRIMARY_KEY_TYPE_RELATIONAL,
//                PRIMARY_KEY_TYPE_OBJECT, rf.getDeclaringClass(),
//                rf.getName(), false);       // change to false 1:N
//        generatetField.put(refTableName, fields);
//    }


    private void addType1ToN(String className, Class rc, Field rf) {
        logger.trace("addType1ToN(" + className + ", " + rc.getName() + ", " + 
                rf.getName() + ");");
        // addType1ToN(bigcompanyportal.Secretary, bigcompanyportal.Boss, secretary
        GeneratedDatabaseFields genFields = generatetField.get(rc.getName());
        if(genFields == null) {
            genFields = new GeneratedDatabaseFields();
        }
        // add field that refer to primary key
        genFields.addRelationalFieldFK(rc.getName(), className,
                limitNameForeginKey(rc, rf),
                PRIMARY_KEY_TYPE_RELATIONAL, PRIMARY_KEY_TYPE_OBJECT,
                rf.getDeclaringClass(), rf.getName(), false);
        generatetField.put(rc.getName(), genFields);
    }


    private void addTypeMToNCollectionReference(Class rc, Field rf) {
        logger.trace("addTypeMToN");

        if(associativeTables == null) {
            associativeTables = new HashMap<String, GeneratedDatabaseFields>();
        }
        String [] asoccTabNames = generateAssociacionsTableNamesForCollections(rc, rf);
        if(associativeTables.containsKey(asoccTabNames[0]) ||
                associativeTables.containsKey(asoccTabNames[1])) {
            // associacion table exit yet, end of method
        } else {
            // create assoc table
            GeneratedDatabaseFields genField = new GeneratedDatabaseFields();
            genField.addRelationalFieldFK(asoccTabNames[0], rc.getName(),
                    addSuffixToName(limitNameForeginKey(rc, rf), "_1"),
                    PRIMARY_KEY_TYPE_RELATIONAL, PRIMARY_KEY_TYPE_OBJECT,
                    rf.getDeclaringClass(), rf.getName(), false);
            logger.debug("addTypeMToN add field: "+rc.getName() + " " + limitNameForeginKey(rc, rf));
            logger.debug("try add: " + rf.getType().getName() + " gen type "+getGenericTypeFromType(rf));
            Field field = getTabReferencedToThisClass(getGenericTypeFromType(rf), rc.getName());
            logger.debug("field " + field.getType() + " neme "+field.getName());
            genField.addRelationalFieldFK(asoccTabNames[0], getGenericTypeFromType(rf),
                    addSuffixToName(limitNameForeginKey(field.getDeclaringClass(), field), "_2"),
                    PRIMARY_KEY_TYPE_RELATIONAL, PRIMARY_KEY_TYPE_OBJECT, 
                    field.getDeclaringClass(), field.getName(), false); // TODO: it's work? field.getName() ??
            associativeTables.put(asoccTabNames[0], genField);
        }
    }

    private void addTypeMToNArrayReference(Class rc, Field rf) {
        logger.trace("addTypeMToNArrayReference(" + rc.getName() + ", " +
                rf.getName());
        if(associativeTables == null) {
            associativeTables = new HashMap<String, GeneratedDatabaseFields>();
        }
        String [] asoccTabNames = generateAssociacionsTableNamesForArrays(rc, rf);
        if(associativeTables.containsKey(asoccTabNames[0]) ||
                associativeTables.containsKey(asoccTabNames[1])) {
            // associacion table exit yet, end of method
        } else {
            // create assoc table
            GeneratedDatabaseFields genField = new GeneratedDatabaseFields();
            genField.addRelationalFieldFK(asoccTabNames[0], rc.getName(),
                    addSuffixToName(limitNameForeginKey(rc, rf), "_1"),
                    PRIMARY_KEY_TYPE_RELATIONAL, PRIMARY_KEY_TYPE_OBJECT,
                    rf.getDeclaringClass(), rf.getName(), false);
            logger.debug("addTypeMToN add field: "+rc.getName() + " " + limitNameForeginKey(rc, rf));
            logger.debug("try add: " + rf.getType().getName() + " gen type "+getArrayTypeFromField(rf));
            Field field = getTabReferencedToThisClass(getArrayTypeFromField(rf), rc.getName());
            logger.debug("field " + field.getType() + " neme "+field.getName());
            genField.addRelationalFieldFK(asoccTabNames[0], getArrayTypeFromField(rf),
                    addSuffixToName(limitNameForeginKey(field.getDeclaringClass(), field), "_2"),
                    PRIMARY_KEY_TYPE_RELATIONAL, PRIMARY_KEY_TYPE_OBJECT,
                    field.getDeclaringClass(), field.getName(), false); // TODO: it's work? field.getName() ??
            associativeTables.put(asoccTabNames[0], genField);
        }
    }

    private String [] generateAssociacionsTableNamesForCollections(Class rc, Field rf) {
        String class2Name = getGenericTypeFromType(rf);
        return new String [] {limitNameForClassesNamesAsoc(ASOCIACION_TABLE_PREFIX +
                    rc.getName() + "_" + class2Name),
                limitNameForClassesNamesAsoc(ASOCIACION_TABLE_PREFIX + class2Name +
                    "_" + rc.getName())
        };
    }

    private String [] generateAssociacionsTableNamesForArrays(Class rc, Field rf) {
        String class2Name = getArrayTypeFromField(rf);
        return new String [] {limitNameForClassesNamesAsoc(ASOCIACION_TABLE_PREFIX +
                    rc.getName() + "_" + class2Name),
                limitNameForClassesNamesAsoc(ASOCIACION_TABLE_PREFIX + class2Name +
                    "_" + rc.getName())
        };
    }

    /**
     * Generate name of relational field created on fr field and related to rc
     * @param rc ReflectClass that relate foreign key
     * @param rf ReflectField for that field is foreign key created
     * @return String: ADDED_FOREIGN_KEY_PREFIX + rf.getName() + "_" + rc.getName()
     */
    private String limitNameForeginKey(Class rc, Field rf) {
        return limitNameForFields(ADDED_FOREIGN_KEY_PREFIX + rf.getName() + "_" + rc.getName());
    }

    private String limitNameForeginKey(Class rc, String fieldName) {
        return limitNameForFields(ADDED_FOREIGN_KEY_PREFIX + fieldName + "_" + rc.getName());
    }

    /**
     * Translate object data type to relational data type.
     * @param objType Object datatype.
     * @return String with relational database type
     */
    private String translateObjectTypeToRelationalType(String objType) {
        Map<String, String> translateMap = getTranslateTypesMap();
        String result = translateMap.get(objType);
        if(result == null) {
            return "Unkwow_" + objType;
        }
        return result;
    }

    private void showInfoAboutReflectClass(Class rc) {
        System.out.println("RC name: " + rc.getName() +
                " componentType name: " + rc.getComponentType() +
                //" delegate name" + rc.getDelegate().getName() +
                " super class: " + rc.getSuperclass());
                //" isCollection: " + rc.isCollection());
    }

    private void showInfoAboutReflectField(Field rf) {
        System.out.println("RF name " + rf.getName() + " type:" + rf.getType().getName() );//+
               // " indexType: " + rf.indexType() );
    }

    /**
     * Test class storedClassName has tab field of type tabRefname.
     * @param storedClassName Name of class.
     * @param tabRefName Name of tab type.
     * @return True if class storedClassName have a tab witch tabRefName objects, false otherwise.
     */
    private boolean hasClassTabReferencedToThisClass(String storedClassName, String tabRefName) {
        logger.trace("hasClassTabReferencedToThisClass(" + storedClassName +", "+tabRefName);
        for(Class rc : loadedClassSet) {
            if(rc.getName().equals(storedClassName)) {
                for(Field rf : rc.getDeclaredFields()) {
                    if(rf.getType().isArray()) {
                        logger.debug("rf.getType().getName(): " + rf.getType().getName() + " test with " + tabRefName);
                        if(getArrayTypeFromField(rf).equals(tabRefName)) {
                            return true;
                        }
                    } else if(isFieldCollection(rf)) {
                        Type genericType = rf.getGenericType();
                        if(genericType != null && getGenericTypeFromType(genericType) != null &&
                                getGenericTypeFromType(genericType).equals(tabRefName)) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private Field getTabReferencedToThisClass(String storedClassName, String tabRefName) {
        Field field = null;
        logger.trace("getTabReferencedToThisClass(" + storedClassName +", "+tabRefName);
        out:
        for(Class rc : loadedClassSet) {
            if(rc.getName().equals(storedClassName)) {
                for(Field rf : rc.getDeclaredFields()) {
                    if(rf.getType().isArray()) {
                        if(getArrayTypeFromField(rf).equals(tabRefName)) {
                            field = rf;
                            break out;
                        }
                    } else if(isFieldCollection(rf)) {
                        Type genericType = rf.getGenericType();
                        //System.out.println("genericType.getClass().getName(): " + genericType.getClass().getName());
                        System.out.println("getGenericTypeFromType(genericType): "+getGenericTypeFromType(genericType));
                        if(genericType != null && getGenericTypeFromType(genericType) != null &&
                                getGenericTypeFromType(genericType).equals(tabRefName)) {
                            field = rf;
                            break out;
                        }
                    }
                }
            }
        }
        return field;
    }


    private Map<String, String> getTranslateTypesMap() {
        if(translateTypesMap == null) {
            translateTypesMap = new HashMap<String, String>();
            for(int i=0; i < TRANSLATE_TYPES_TAB.length; i++) {
                translateTypesMap.put(TRANSLATE_TYPES_TAB[i][0], TRANSLATE_TYPES_TAB[i][1]);
            }
        }
        return translateTypesMap;
    }

    /**
     * Test is class name in typeName user stored class.
     * @param typeName Name of tested class.
     * @return True if class is user stored class, false otherwise.
     */
    private boolean isUserStoredClass(String typeName) {
        //logger.trace("isUserStoredClass("+typeName+")");
        for(Class rc : loadedClassSet) {
            if(rc.getName().equals(typeName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Test is field a Collection. Suported Collection names are in
     * COLLECTIONS_CLASS_NAMES tab.
     * @param field Field object to test.
     * @return true if field is collection, false otherwise.
     */
    public boolean isFieldCollection(Field field) {
        Class [] classes = field.getType().getInterfaces();
        List<String> collectionsList = Arrays.asList(COLLECTIONS_CLASS_NAMES);
        for(Class c : classes) {
            if(collectionsList.contains(c.getName())) {
                //logger.trace("isFieldCollection true for: "+c.getName() + " field "+field.getName());
                return true;
            }
        }
        return false;
    }

    /**
     * Translate Type to generic type i.e. for: java.util.List<obj2relTest.Animal>
     * method return obj2relTest.Animal
     * @param type Type.
     * @return GenericType (if exist), null otherwise
     */
    private String getGenericTypeFromType(Type type) {
        String typeStr = type.toString();
        int start = typeStr.indexOf("<") + 1;
        int end = typeStr.indexOf(">");
        return typeStr.substring(start, end);
    }

    /**
     * Translate Type to generic type i.e. for: java.util.List<obj2relTest.Animal>
     * method return obj2relTest.Animal. This method invoke
     * getGenericTypeFromType(Type type) method.
     * @param rf Field object.
     * @return String with generic type name.
     */
    public  String getGenericTypeFromType(Field rf) {
        return getGenericTypeFromType(rf.getGenericType());
    }

    /**
     * Method that generate create script for associacions tables;
     * @param out
     * @param fields
     */
    private void generateScriptCreateTable(PrintWriter out, GeneratedDatabaseFields fields, String tableName) {
        out.append("create table \"" + tableName + "\"(" + NEW_LINE);
        Iterator<ForeignKey> iter =  fields.getForeignKeys().iterator();
        for(int i=0; i < fields.getForeignKeys().size(); i++) {
            if(iter.hasNext()) {
                ForeignKey foreignKey = iter.next();
                out.append(INDENT + "\"" + foreignKey.getName() + "\"" +  INDENT +
                        foreignKey.getRelationalType());
                if(i != fields.getForeignKeys().size() - 1) {
                    out.append(", "+NEW_LINE);
                } else {
                    if(fields.getRelationalColumns() != null &&
                            !fields.getRelationalColumns().isEmpty()) {
                        out.append(", "+NEW_LINE);
                    } else {
                        out.append(NEW_LINE);
                    }
                }
            }
        }
        
        if(fields.getRelationalColumns() != null) {
            Iterator<RelationalColumn> iterCol = fields.getRelationalColumns().iterator();
            for (int i = 0; i < fields.getRelationalColumns().size(); i++) {
                RelationalColumn relationalColumn = iterCol.next();
                out.append(INDENT + "\"" + relationalColumn.getName() + "\"" +
                        INDENT + relationalColumn.getRelationalType());
                if(i != fields.getRelationalColumns().size() - 1) {
                    out.append(", " + NEW_LINE);
                } else {
                    out.append(NEW_LINE);
                }
            }
        }

        out.append(");" + NEW_LINE + NEW_LINE);
    }

    private void generateInheritanceInformation() {
        for(Class cl : loadedClassSet) {
//            if(isUserStoredClass(cl.getSuperclass().getName())
//                    && getRealSuperClass(cl) != null) {
            if(getRealSuperClass(cl) != null && 
                    isUserStoredClass(getRealSuperClass(cl).getName())) {
                //System.out.println("Mamy dziedzczenie "+cl.getName() + " super: "+cl.getSuperclass().getName());
                GeneratedDatabaseFields fields = generatetField.get(cl.getName());
                if(fields == null) {
                    fields = new GeneratedDatabaseFields();
                }
                fields.addRelationalFieldFK(cl.getName(), getRealSuperClass(cl).getName(),
                        //generateInheritanceAtributeName(cl.getSuperclass()),
                        generateInheritanceAtributeName(getRealSuperClass(cl)),        // resolve boss integrity problem
                        PRIMARY_KEY_TYPE_RELATIONAL, PRIMARY_KEY_TYPE_OBJECT,
                        null, null, false);
                generatetField.put(cl.getName(), fields);
            }
        }
    }

    private String generateInheritanceAtributeName(Class cl) {
        return limitNameForFields(INHERITANCE_KEY_PREFIX + cl.getName());
    }

    /**
     * Create name for columns in relational database for object fields. Name
     * of columns can't contain dot '.' char and have max length
     * (MAX_NUMBER_CHARS_IN_NAME).
     * @param name Name of column.
     * @return Name of column.
     */
    private String limitNameForFields(String name) {
        name = name.replace('.', '_');
        if(name.length() > MAX_NUMBER_CHARS_IN_NAME)
            return name.substring(0, MAX_NUMBER_CHARS_IN_NAME);
        else
            return name;
    }

    /**
     * Calculate number of fields that will have generate atribute in relational
     * database.
     * @param declaredFields Fields tab.
     * @return number of fields that will have generate atribute in relational
     * database.
     */
    public int calculateNumberOfGeneratedFields(Field[] declaredFields) {
        int result = 0;
        for(int i=0; i < declaredFields.length; i++) {
            Field rf = declaredFields[i];
            if(isGeneratedColumnForField(rf)) {
                result++;
            }
        }
        return result;
    }

    /**
     * Contact two strings, str1 can be cuted that result string leght is
     * smaller for MAX_NUMBER_CHARS_IN_NAME.
     * For example: "VERY_VERY_LONG_STRING_NAME_FIRST_PART" and "SECOND" will be return
     * VERY_VERY_LONG_STRING_NSECOND
     * @param name Name for that is suffix added.
     * @param suffix Suffix of new name.
     * @return Concatenate of two Strings cutted for MAX_NUMBER_CHARS_IN_NAME.
     */
    private String addSuffixToName(String name, String suffix) {
        if(name.length() + suffix.length() > MAX_NUMBER_CHARS_IN_NAME) {
            return name.substring(0, MAX_NUMBER_CHARS_IN_NAME - suffix.length()) + suffix;
        } else {
            return name + suffix;
        }
    }

    /**
     * Generate relational database script that create Referential Integrity.
     * This script sould be run after execute create relational database script
     * and after fill database of data.
     * @param outputFileName
     * @param loadedClassSet
     * @throws CodeGenerationException
     */
    public void generateReferentialIntegrityRelationalDatabaseScript(String outputFileName,
            Set<Class> loadedClassSet) throws CodeGenerationException {

        File file = new File(outputFileName);
        try {
            file.createNewFile();
        } catch(IOException e) {
            throw new CodeGenerationException(e.getMessage());
        }

        if(!file.isFile()) {
            String message = I18n.getString("thispathisnotafile");
            message = String.format(message, file.getAbsolutePath());
            throw new CodeGenerationException(message);
        }

        File outputFile = new File(outputFileName);
        String msg = I18n.getString("generatedscriptpath");
        msg = String.format(msg, outputFile.getAbsolutePath());
        logger.debug(msg);
        try {
            PrintWriter out = new PrintWriter(outputFile);
            generateScriptReferentialIntegrity(out, loadedClassSet);
            logger.info(I18n.getString("realationaldatabasescriptgeneratesucess"));
        } catch(FileNotFoundException e) {
            String message = I18n.getString("filenotfound");
            message = String.format(message, outputFile.getAbsolutePath());
            throw new CodeGenerationException(message);
        }
    }

    private void generateScriptReferentialIntegrity(PrintWriter out, Set<Class> loadedClassSet) {
        logger.trace("generateReferentialIntegrity(out, loadedClassSet);");
        for(Class cl : loadedClassSet) {
            if(hasClassDeclaredFields(cl)) {
                // generate unique field for primary keys
                out.append("alter table \"" + limitNameForMainClasses(cl.getName()) +
                        "\" modify" + NEW_LINE + INDENT + "(\"" +
                        PRIMARY_KEY_NAME + "\" " + PRIMARY_KEY_TYPE_RELATIONAL +
                        " NOT NULL);" + NEW_LINE + NEW_LINE);

                // generate primary keys
                out.append("alter table \"" + limitNameForMainClasses(cl.getName()) +
                        "\" add constraint \"" +
                        generatePKName(PRIMARY_KEY_NAME,
                        limitNameForMainClasses(cl.getName())) + "\" " +
                        NEW_LINE + INDENT + " PRIMARY KEY (\"" + PRIMARY_KEY_NAME +
                        "\");" + NEW_LINE + NEW_LINE);
            }
        }

        

        // generate foreign keys
        for(Map.Entry<String, GeneratedDatabaseFields> entry : generatetField.entrySet()) {
            if(isPrimitiveType(entry.getKey())) {
                logger.trace("Class " + entry.getKey() + " primituve, continue");
                continue;
            }
            GeneratedDatabaseFields fields = entry.getValue();
            int numberOfForeginKey = 1;
            for(ForeignKey fk : fields.getForeignKeys()) {
                if(hasClassDeclaredFields(getClassFromLoadedClassSet(entry.getKey()))) {
                    System.out.println("aaa for " + fk.getName() + " ref table name " + fk.getReferencedTableName() + " owner table " + fk.getOwnerTable());
                    // make unique froegin keys
                    if(fk.isUnique()) {
//                        out.append("alter table \"" + limitNameForMainClasses(entry.getKey()) + "\"" +
//                                " modify" + NEW_LINE + INDENT + "(\"" +
//                            fk.getName() + "\"" +
//                            " NOT NULL UNIQUE);" + NEW_LINE + NEW_LINE);
                    }
                    
                    // integrity foregin and primary keys
                    out.append("/* boss problem */" + NEW_LINE);
                    out.append("alter table \"" + limitNameForMainClasses(entry.getKey()) +
                            "\"" + NEW_LINE);
                    out.append(INDENT + "add constraint \"" + 
                            generateFKName(fk.getName(), entry.getKey(), numberOfForeginKey) +
                            "\" foreign key (\"" + fk.getName() + "\")" + NEW_LINE);
                    out.append(INDENT + INDENT + "references \"" +
                            limitNameForMainClasses(fk.getReferencedTableName()) + "\" (\"" +    // ??
//                            limitNameForMainClasses(getRealSuperClass(
//                                getClassFromLoadedClassSet(
//                                    fk.getReferencedTableName())).getName()) + "\" (\"" +    // ??
                            fk.getReferencedTablePrimaryKey() + "\");" +
                            NEW_LINE + NEW_LINE);
                    numberOfForeginKey++;
                }
            }
        }

        // generate foreign keys for associaons tables
        if(associativeTables != null) {
            for(Map.Entry<String,GeneratedDatabaseFields> entry : associativeTables.entrySet()) {
                // generate unique field for primary keys
                GeneratedDatabaseFields fields = entry.getValue();
                int numberOfForeginKey = 1;
                for(ForeignKey fk : fields.getForeignKeys()) {
                    // make unique froegin keys
                    if(fk.isUnique()) {
                        out.append("alter table \"" + limitNameForMainClasses(entry.getKey()) + "\"" +
                                " modify" + NEW_LINE + INDENT + "(\"" +
                            fk.getName() + "\" " + fk.getRelationalType() +
                            " NOT NULL UNIQUE);" + NEW_LINE + NEW_LINE);
                    }

                    // integrity foregin and primary keys
                    out.append("alter table \"" + limitNameForMainClasses(entry.getKey()) + "\"" + NEW_LINE);
                    out.append(INDENT + "add constraint \"" + generateFKName(
                            fk.getName(), entry.getKey(), numberOfForeginKey) +
                            "\" foreign key (\"" + fk.getName() + "\")" + NEW_LINE);
                    out.append(INDENT + INDENT + "references \"" +
                            limitNameForMainClasses(fk.getReferencedTableName()) + "\" (\"" +
                            fk.getReferencedTablePrimaryKey() + "\");" +
                            NEW_LINE + NEW_LINE);
                    numberOfForeginKey++;
                }
            }
        }

        out.close();
    }

    private String generateFKName(String name, String tableName, int numberOfForeginKey) {
        if(name.length() + FOREGIN_KEY_PREFIX.length() +
                String.valueOf(numberOfForeginKey).length() > MAX_NUMBER_CHARS_IN_NAME) {
            int nameEnd = MAX_NUMBER_CHARS_IN_NAME - PRIMARY_KEY_PREFIX.length() - 
                    String.valueOf(numberOfForeginKey).length() - 
                    (tableName.length() - tableName.length() / 2);
            return FOREGIN_KEY_PREFIX + tableName.substring(tableName.length() / 2, tableName.length()) +
                    name.substring(0,
                    (nameEnd > name.length() ? name.length() : nameEnd)) + numberOfForeginKey;
        } else {
            return FOREGIN_KEY_PREFIX + name + numberOfForeginKey;
        }
    }

    private String generatePKName(String name, String tableName) {
        if(name.length() + PRIMARY_KEY_PREFIX.length() + tableName.length() > MAX_NUMBER_CHARS_IN_NAME) {
            int nameEnd = MAX_NUMBER_CHARS_IN_NAME - PRIMARY_KEY_PREFIX.length() - (tableName.length() - tableName.length() / 2);
            return PRIMARY_KEY_PREFIX + tableName.substring(tableName.length() / 2, tableName.length()) +
                    name.substring(0, 
                    (nameEnd > name.length() ? name.length() : nameEnd));
        } else {
            return PRIMARY_KEY_PREFIX + tableName + name;
        }
    }

    public Map<String, GeneratedDatabaseFields> getAssociativeTables() {
        return associativeTables;
    }

    public Map<String, GeneratedDatabaseFields> getGeneratetField() {
        return generatetField;
    }

    public Set<Class> getLoadedClassSet() {
        return loadedClassSet;
    }

    public void unloadClasses() {
        associativeTables = null;
        generatetField = null;
        loadedClassSet = null;
    }

    /**
     * Limit names for generated associative classes.
     * @param name Orginal name.
     * @return Name cutted to String length less MAX_NUMBER_CHARS_IN_NAME. First
     * part of String is left there.
     */
    private String limitNameForClassesNamesAsoc(String name) {
        if(name.length() > MAX_NUMBER_CHARS_IN_NAME)
            return name.substring(0, MAX_NUMBER_CHARS_IN_NAME);
        else
            return name;
    }

    /**
     * Limit names for generated persistence classes. In relational databases
     * names of tables has max length. We must cut name to
     * MAX_NUMBER_CHARS_IN_NAME length.
     * @param name Orginal class name.
     * @return Name cutted to String length less MAX_NUMBER_CHARS_IN_NAME. Last 
     * part of String is left there. This String can be name of table.
     */
    public String limitNameForMainClasses(String name) {
        if(name.length() > MAX_NUMBER_CHARS_IN_NAME)
            return name.substring(name.length() - MAX_NUMBER_CHARS_IN_NAME, name.length());
        else
            return name;
    }

    /**
     * Test is class name a primitive type.
     * @param name Name of class (or primitive).
     * @return true if name is name of primitive type in Java, false otherwise.
     */
    public boolean isPrimitiveType(String name) {
        return PRIMITIVES_NAMES.contains(name);
    }

//    /**
//     * Test is field primitive wraper type. This method invoke:
//     * isFieldPrimitiveWrapper(String fieldTypeName)
//     * @param field Field object to test.
//     * @return true if field type name is in wrappersTypes tab, false otherwise.
//     */
//    public boolean isFieldPrimitiveWrapper(Field field) {
//        String type = field.getType().getName();
//        return isFieldPrimitiveWrapper(type);
//    }

    /**
     * Test is field type name a wraper type. This method check is fieldTypeName
     * contain in wrappersTypes tab.
     * @param fieldTypeName String represents field type name.
     * @return true if fiedl type name is wraper type (in wrappersTypes tab),
     * false otherwise.
     */
    public boolean isFieldPrimitiveWrapper(String fieldTypeName) {
        for (String s : wrappersTypes) {
            if(s.equals(fieldTypeName)) {
                return true;
            }
        }
        return false;
    }

    private boolean isFieldAnotherSimpleSuportedType(String fieldTypeName) {
        for (String s : anotherSimpleSuprtedTypes) {
            if(s.equals(fieldTypeName)) {
                return true;
            }
        }
        return false;

    }

    /**
     * Method test that Class object have declared fields. If class has no 
     * fields table sould not genere for this class.
     * @param rc Class object.
     * @return True if class have one or more declared fields, false otherwise.
     */
    public boolean hasClassDeclaredFields(Class rc) {
        // first condition add becouse NullPointerException thrown in
        // Object2RelationalMigrationObjWrapersTest
        if(rc == null || rc.getDeclaredFields().length == 0) {
            logger.trace("hasClassDeclaredFields(null) = false");
            return false;
        }
        logger.trace("hasClassDeclaredFields(" + rc.getName() + ") = true");
        return true;
    }

    /**
     * Return real super class. If super class hasn't declared fields method
     * return next superclass in inhertance hierarchy.
     * @param cl Class object.
     * @return Super class, or another Class from inhertance hierarchy, or null
     * if class hasn't real superclass.
     */
    private Class getRealSuperClass(Class cl) {
        logger.trace("getRealSuperClass(" + cl.getName() + ")");
        Class result = null;
        Class c = cl;
        while(c != null) {
            if(c.getSuperclass().getName().equals("java.lang.Object")) {
                return null;
            }
            if(hasClassDeclaredFields(c.getSuperclass())) {
                result = c.getSuperclass();
                break;
            }
            c = c.getSuperclass();
        }
        return result;
    }

    /**
     * Get Class object from loadedClassSet by name, or null if not exist.
     * @param key Class name.
     * @return Class object, or null.
     */
    private Class getClassFromLoadedClassSet(String key) {
        for(Class cl : loadedClassSet) {
            if(cl.getName().equals(key)) {
                return cl;
            }
        }
        return null;
    }

    /**
     * Test is for field generate column in Relational Database. Field is
     * generated, only if field isn't array, collection and user stored class.
     * @param field Tested field.
     * @return false if field isn't array, collection and user stored class,
     * true otherwise.
     */
    public boolean isGeneratedColumnForField(Field field) {
        String typeName = field.getType().getName();
        if(field.getType().isArray()) {
            logger.debug(typeName + " typ tablicowy: " + field.getName() +" nie generujemy pola");
            return false;
        } else if(isFieldCollection(field)) {
            logger.debug(typeName + " typ kolecji: " + field.getName() +" nie generujemy pola");
            return false;
        } else if(isUserStoredClass(typeName)) {
            logger.debug(typeName + " ref do innego obiektu: " + field.getName() +" nie generujemy pola");
            return false;
        } else if(isFieldStatic(field)) {
            logger.debug(typeName + " pole statyczne - nie generujemy pola");
            return false;
        }
        return true;
    }

    /**
     * Add table to database for tab of primitives.
     * @param primitiveType Primitive type of values in tab.
     * @param rc Class object where is tab of primitives.
     * @param rf Tab field for is created relational table.
     */
    private void addTableForPrimitiveArray(String primitiveType, Class rc, Field rf) {
        logger.trace("addTableForPrimitiveTab(" + primitiveType + ", " +
                rc.getName() + ", " + rf.getName() + ");");
        // byte, com.mycompany.db4oprimitives.MyPrimitives, tabByte

        if(associativeTables == null) {
            associativeTables = new HashMap<String, GeneratedDatabaseFields>();
        }

        String tableName = limitNameTableForTabOfPrimitives(rc, rf);

        GeneratedDatabaseFields generatedDatabaseFields = new GeneratedDatabaseFields();
        generatedDatabaseFields.addRelationalFieldFK(tableName,
                limitNameForMainClasses(rc.getName()), limitNameForeginKey(rc, rf),
                PRIMARY_KEY_TYPE_RELATIONAL, PRIMARY_KEY_TYPE_OBJECT,
                rf.getDeclaringClass(), rf.getName(), false);
        generatedDatabaseFields.addRelationalColumn(tableName, rf.getName(),
                translateObjectTypeToRelationalType(primitiveType), primitiveType + " []");
        // index column
        generatedDatabaseFields.addRelationalColumn(tableName,
                limitNameForArrayIndex(tableName, rf.getName()), 
                INDEX_RELATIONAL_TYPE, INDEX_OBJECT_TYPE);

        associativeTables.put(tableName, generatedDatabaseFields);

        // add to objectsToAfterSave
        if(objectsToAfterSave == null) {
            objectsToAfterSave = new HashMap<String, List<ClassToSaveAfter>>();
        }
        List<ClassToSaveAfter> objectToSave = objectsToAfterSave.get(rc.getName());
        if(objectToSave == null) {
            objectToSave = new ArrayList<ClassToSaveAfter>();
        }
        ClassToSaveAfter classToSaveAfter = new ClassToSaveAfter();
        classToSaveAfter.setFormWhereSaveClassName(rc.getName());
        classToSaveAfter.setToSaveClassName(tableName);
        classToSaveAfter.setForeginKeyFieldName(limitNameForeginKey(rc, rf));
        classToSaveAfter.setSetMethodAtributeName(rf.getName());
        objectToSave.add(classToSaveAfter);
        objectsToAfterSave.put(rc.getName(), objectToSave);
    }

    /**
     * Add table to database for tab of primitives wrappers.
     * This method is very like addTableForPrimitiveTab(...) method. Maybe refactoring??
     * @param wraperType Primitive wraper type of values in tab.
     * @param rc Class object where is tab of primitives wrapers.
     * @param rf Tab field for is created relational table.
     */
    private void addTableForWrapersArray(String wraperType, Class rc, Field rf) {
        //TODO: This method is very like addTableForPrimitiveTab(...) method. Maybe refactoring??
        logger.trace("addTableForWrapersTab(" + wraperType + ", " +
                rc.getName() + ", " + rf.getName() + ");");
        // Byte, com.mycompany.db4oprimitives.MyPrimitives, tabByte

        if(associativeTables == null) {
            associativeTables = new HashMap<String, GeneratedDatabaseFields>();
        }

        String tableName = limitNameTableForTabOfPrimitives(rc, rf);

        GeneratedDatabaseFields generatedDatabaseFields = new GeneratedDatabaseFields();
        generatedDatabaseFields.addRelationalFieldFK(tableName,
                limitNameForMainClasses(rc.getName()), limitNameForeginKey(rc, rf),
                PRIMARY_KEY_TYPE_RELATIONAL, PRIMARY_KEY_TYPE_OBJECT,
                rf.getDeclaringClass(), rf.getName(), false);
        generatedDatabaseFields.addRelationalColumn(tableName, rf.getName(),
                translateObjectTypeToRelationalType(wraperType), wraperType + " []");
        // index column
        generatedDatabaseFields.addRelationalColumn(tableName,
                limitNameForArrayIndex(tableName, rf.getName()),
                INDEX_RELATIONAL_TYPE, INDEX_OBJECT_TYPE);

        associativeTables.put(tableName, generatedDatabaseFields);

        // add to objectsToAfterSave
        if(objectsToAfterSave == null) {
            objectsToAfterSave = new HashMap<String, List<ClassToSaveAfter>>();
        }
        List<ClassToSaveAfter> objectToSave = objectsToAfterSave.get(rc.getName());
        if(objectToSave == null) {
            objectToSave = new ArrayList<ClassToSaveAfter>();
        }
        ClassToSaveAfter classToSaveAfter = new ClassToSaveAfter();
        classToSaveAfter.setFormWhereSaveClassName(rc.getName());
        classToSaveAfter.setToSaveClassName(tableName);
        classToSaveAfter.setForeginKeyFieldName(limitNameForeginKey(rc, rf));
        classToSaveAfter.setSetMethodAtributeName(rf.getName());
        objectToSave.add(classToSaveAfter);
        objectsToAfterSave.put(rc.getName(), objectToSave);
    }

    /**
     * Add table to database for tab of primitives wrappers.
     * This method is very like addTableForPrimitiveTab(...) and addTableForWrapersTab(...) method. Maybe refactoring??
     * @param anotherSipleType Primitive wraper type of values in tab.
     * @param rc Class object where is tab of primitives wrapers.
     * @param rf Tab field for is created relational table.
     */
    private void addTableForAnotherSimpleSuportedtype(String anotherSipleType, Class rc, Field rf) {
        //TODO: This method is very like addTableForPrimitiveTab(...) method. Maybe refactoring??
        logger.trace("addTableForWrapersTab(" + anotherSipleType + ", " +
                rc.getName() + ", " + rf.getName() + ");");
        // java.util.BigDecimal, com.mycompany.db4oprimitives.MyPrimitives, tabBigdecimal

        if(associativeTables == null) {
            associativeTables = new HashMap<String, GeneratedDatabaseFields>();
        }

        String tableName = limitNameTableForTabOfPrimitives(rc, rf);

        GeneratedDatabaseFields generatedDatabaseFields = new GeneratedDatabaseFields();
        generatedDatabaseFields.addRelationalFieldFK(tableName,
                limitNameForMainClasses(rc.getName()), limitNameForeginKey(rc, rf),
                PRIMARY_KEY_TYPE_RELATIONAL, PRIMARY_KEY_TYPE_OBJECT,
                rf.getDeclaringClass(), rf.getName(), false);
        generatedDatabaseFields.addRelationalColumn(tableName, rf.getName(),
                translateObjectTypeToRelationalType(anotherSipleType), anotherSipleType + " []");
        // index column
        generatedDatabaseFields.addRelationalColumn(tableName,
                limitNameForArrayIndex(tableName, rf.getName()),
                INDEX_RELATIONAL_TYPE, INDEX_OBJECT_TYPE);

        associativeTables.put(tableName, generatedDatabaseFields);

        // add to objectsToAfterSave
        if(objectsToAfterSave == null) {
            objectsToAfterSave = new HashMap<String, List<ClassToSaveAfter>>();
        }
        List<ClassToSaveAfter> objectToSave = objectsToAfterSave.get(rc.getName());
        if(objectToSave == null) {
            objectToSave = new ArrayList<ClassToSaveAfter>();
        }
        ClassToSaveAfter classToSaveAfter = new ClassToSaveAfter();
        classToSaveAfter.setFormWhereSaveClassName(rc.getName());
        classToSaveAfter.setToSaveClassName(tableName);
        classToSaveAfter.setForeginKeyFieldName(limitNameForeginKey(rc, rf));
        classToSaveAfter.setSetMethodAtributeName(rf.getName());
        objectToSave.add(classToSaveAfter);
        objectsToAfterSave.put(rc.getName(), objectToSave);
    }

    /**
     * Generate name for table for tab of primitives.
     * @param rc Class that have tab of primitives.
     * @param rf Field that is tab of primitives.
     * @return Name for that class named MyClass, and field named myTabField
     * method return TAB_MyClass_myTabField
     */
    private String limitNameTableForTabOfPrimitives(Class rc, Field rf) {
        String tablename;
        if(PRIMITIVE_TAB_NAME_PREFIX.length() + rc.getName().length() + rf.getName().length() + 1
                > MAX_NUMBER_CHARS_IN_NAME) {
            tablename = rc.getName() + "_" + rf.getName();
            tablename = tablename.substring(
                    tablename.length() - (MAX_NUMBER_CHARS_IN_NAME - PRIMITIVE_TAB_NAME_PREFIX.length()),
                    tablename.length());
            tablename = PRIMITIVE_TAB_NAME_PREFIX + tablename;
        } else {
            tablename = PRIMITIVE_TAB_NAME_PREFIX + rc.getName() + rf.getName();
        }

        return tablename;
    }

    public Map<String, List<ClassToSaveAfter>> getObjectsToAfterSave() {
        return objectsToAfterSave;
    }

    /**
     * Test is field static.
     * @param field Tested field object.
     * @return true if field is static, false otherwise.
     */
    private boolean isFieldStatic(Field field) {
        int modifiers = field.getModifiers();
        if(Modifier.isStatic(modifiers)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Generate method name to get another object from class. This method name
     * is used to get referenced objects (relation 1:1 and 1:N) from db4o
     * database.
     * @param fk ForeginKey object that have all information needed to generate
     * this method name. This ForeginKey object should refer to antoher class
     * where it is.
     * @return Method name to get refrenced objects from db4o database.
     */
    public String limitNameForMethodToGetReferencedObjectsFromDb4o(ForeignKey fk) {
        return "get" + lastPartOfClassName(fk.getReferencedTableName()) + "By" +
                lastPartOfClassName(fk.getOwnerTable()) + "FromDb4o";
    }

    /**
     * Method generate String that is last part of class name (no package name
     * before). Thos method cut package name.
     * @param className Class name.
     * @return Only class name (no package name).
     */
    public String lastPartOfClassName(String className) {
        if(className.lastIndexOf(".") != (-1)) {
            return className.substring(className.lastIndexOf(".") + 1,
                    className.length());
        } else {
            return className;
        }
    }

    //TODO: Test addTypeMtoNForAssociacion1toNArrayReference
    private void addTypeMtoNForAssociacion1toNArrayReference(String arrayType, Class rc, Field rf) {
        addTypeMtoNForAssociacion1toNCollecitonReference(arrayType, rc, rf);
    }

    //TODO: test addTypeMtoNForAssociacion1toNCollecitonReference
    private void addTypeMtoNForAssociacion1toNCollecitonReference(String genericType, Class rc, Field rf) {
        logger.trace("addTypeMtoNForAssociacion1toNCollecitonReference(" +
                genericType +", " + rc.getName() + ", " + rf.getName() + ");");
        // obj2relTest.PhotoGallery, obj2relTest.AppUser, photoGallerys
        
        if(associativeTables == null) {
            associativeTables = new HashMap<String, GeneratedDatabaseFields>();
        }
        String tableName = generateAssociacionTableName1toN(rc.getName(), rf.getName());
        //String [] asoccTabNames = generateAssociacionsTableNamesForCollections(rc, rf);
        logger.debug("tableName: " + tableName);

            // create assoc table
            GeneratedDatabaseFields genField = new GeneratedDatabaseFields();
            genField.addRelationalFieldFK(tableName, rc.getName(),
                    addSuffixToName(limitNameForeginKey(rc, rf), "_1"),
                    PRIMARY_KEY_TYPE_RELATIONAL, PRIMARY_KEY_TYPE_OBJECT,
                    rf.getDeclaringClass(), rf.getName(), false);
            // addRelationalFieldFK(ASOC_obj2relTest.AppUser_tasks, obj2relTest.AppUser,
            // GEN_tasks_obj2relTest_AppUse_1, NUMBER(19), long, tasks, false);

            logger.debug("addTypeMToN add field: "+rc.getName() + " " + limitNameForeginKey(rc, rf));
            // logger.debug("try add: " + rf.getType().getName() + " gen type "+ get getGenericTypeFromType(rf));
            //Field field = getTabReferencedToThisClass(getGenericTypeFromType(rf), rc.getName());
            //logger.debug("field " + field.getType() + " neme "+field.getName());
            genField.addRelationalFieldFK(tableName, genericType,
                    addSuffixToName(limitNameForeginKey(rf.getDeclaringClass(), PRIMARY_KEY_NAME), "_2"),
                    PRIMARY_KEY_TYPE_RELATIONAL, PRIMARY_KEY_TYPE_OBJECT,
                    null, null, false); // TODO: it's work? field.getName() ??
            // addRelationalFieldFK(ASOC_obj2relTest.AppUser_tasks, obj2relTest.AppUser,
            // GEN_GENERATED_ID_obj2relTest_2, NUMBER(19), long, null, false);

            associativeTables.put(tableName, genField);
        
    }

    private String generateAssociacionTableName1toN(String ownerClassname,
            String fieldName) {
        if(ASOCIACION_TABLE_PREFIX.length() + ownerClassname.length() + fieldName.length() + 1 > MAX_NUMBER_CHARS_IN_NAME) {
            //TODO: may be < 0 test
            int startIndex = ownerClassname.length() - (MAX_NUMBER_CHARS_IN_NAME -
                    ASOCIACION_TABLE_PREFIX.length() - fieldName.length() - 1);
            //logger.debug("startIndex: " + startIndex);// + " another " + (ownerClassname.length() - startIndex));
            String shortOwnerName = ownerClassname.substring(startIndex, ownerClassname.length());
            return ASOCIACION_TABLE_PREFIX + shortOwnerName + "_" + fieldName;
        } else {
            return ASOCIACION_TABLE_PREFIX + ownerClassname + "_" + fieldName;
        }
    }

    public String limitNameForArrayIndex(String tableName, String columnName) {
        if(INDEX_PREFIX.length() + tableName.length() + columnName.length() + 1> MAX_NUMBER_CHARS_IN_NAME) {
            int startIndex = tableName.length() -
                    (MAX_NUMBER_CHARS_IN_NAME - INDEX_PREFIX.length() -
                    columnName.length() - 1);
            String shortTableName = tableName.substring(startIndex);
            String resultName = INDEX_PREFIX + shortTableName + "_" + columnName;
            return resultName.replace('.', '_');
        } else {
            String resultName = INDEX_PREFIX + tableName + "_" + columnName;
            return resultName.replace('.', '_');
        }
    }

}
